/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.sdt.algorithm;

import com.hp.hpl.jena.rdf.model.Property;
import edu.yonsei.iwb.sdt.SDTLearner;
import edu.yonsei.iwb.sdt.SDTLearningType;
import edu.yonsei.iwb.sdt.datastructure.DataStore;
import java.util.Random;
import java.util.Vector;
import edu.yonsei.iwb.sdt.datastructure.DecomposeCandidate;
import edu.yonsei.iwb.sdt.datastructure.DecomposeCandidateSet;
import edu.yonsei.iwb.sdt.datastructure.TargetInstance;
import edu.yonsei.iwb.sdt.datastructure.tree.DEdge;
import edu.yonsei.iwb.sdt.datastructure.tree.DNode;
import edu.yonsei.iwb.sdt.datastructure.tree.NodeType;
import edu.yonsei.iwb.sdt.datastructure.TargetInstanceSet;
import edu.yonsei.iwb.sdt.datastructure.TargetInstanceValueInformation;
import edu.yonsei.iwb.sdt.ontology.ClassModel;
import edu.yonsei.iwb.sdt.ontology.PropertyArc;
import edu.yonsei.iwb.sdt.refinement.ConceptConstructorRefinement;
import edu.yonsei.iwb.sdt.refinement.Refinement;
import edu.yonsei.iwb.sdt.refinement.RefinementType;
import edu.yonsei.iwb.sdt.util.SDTLog;
import edu.yonsei.iwb.sdt.util.UtilFunctions;
import edu.yonsei.iwb.ts.TSSearch;
import edu.yonsei.iwb.ts.model.ObjectNode;
import java.io.IOException;
import java.util.ArrayList;

/**
 *
 * @author DK
 */
public class TreeDecomposer {

//    private SearchDecomposeCandidateSet sdcs;
    private ClassModel classmodel;
    private DataStore ds;
    private int learningOption;
    private TargetInstanceSet tis;
    private double ENTROPY_THRESHOLD;
    private int mincandidate = Integer.MAX_VALUE;
    private int maxcandidate = 0;
    private int loop = 0;
    private int UNITEXPAND = 0;
    private Random ran = new Random();
//    private ResourceWeights rws;

    public TreeDecomposer(DataStore ds, ClassModel classmodel, int opt, TargetInstanceSet tis, double threshold) {
        this.ds = ds;
        this.classmodel = classmodel;
        this.learningOption = opt;
        this.tis = tis;
//        this.rws = new ResourceWeights(model);
        this.ENTROPY_THRESHOLD = threshold;
    }

    public void setinit() {
        this.maxcandidate = 0;
    }

    public int getMaxCandidateNumber() {
        return this.maxcandidate;
    }

    public int getMinCandidateNumber() {
        return this.mincandidate;
    }

    public void decomposeTreeNode(DNode node, DecomposeCandidateSet dcs) throws IOException {
        
        DecomposeCandidateSet seldcs = null;
        if (SDTLearner.FEATURE_SELECTION_METHOD == SDTLearningType.FS1) {   // select random candidates FS1         
            seldcs = this.selectRandomDecomposeCandidate(dcs);
        } else {// select random properties FS2 or FS3
            seldcs = dcs;
        }
        seldcs.calculateInformationGain(node, tis);
        
        if (seldcs.isEmpty()) {
            if(SDTLearner.SEARCHMETHOD == SDTLearningType.NORMALSEARCH){                
                SDTLog.log(SDTLog.DETAIL, "# CandidateSet is not Exist - Node : " +node.NODE_ID +" is Leaf node\n");
                node.NODE_TYPE = NodeType.LEAFNODE;
                node.setFinalClassLabel();                
            }else{
                // Do simulated annealing process
                SDTLog.log(SDTLog.DETAIL, "- # All dc are improper.. Do SA process.. for Node : " + node.NODE_ID+"\n");
                
                double temperature = 1;                                                
                Vector<PropertyArc> hisPAset = dcs.getHistoryPropertySet();
                Vector<PropertyArc> connectedPAset = dcs.getConnectedPropertySet();
//                ArrayList<String> lastResourceSet = dcs.getLastPositionedResourceSet();
                DCSSearcher sdcs = new DCSSearcher(this.ds);
                
                SimulatedAnnealingProcessor sap = new SimulatedAnnealingProcessor(hisPAset, node.getInstanceSet(), connectedPAset, sdcs);
                
                findNewDCS:
                while(true){
                    if (checkOverSearchCondition(node.getEntropy(), temperature)) {
                        DecomposeCandidateSet expandDCS = sap.findExpandedDecomposeCandidateSet();
                        expandDCS.calculateInformationGain(node, tis);                        
                        
                        if(!expandDCS.isEmpty()){
                            DecomposeCandidate bestdc = this.findBestDecomposeCandidate(expandDCS);
                            SDTLog.log(SDTLog.DETAIL, "# Best Decompose Candidate : " + bestdc.explanation()+"...\n");
                            expandDCS.remove(bestdc);
                            this.loop++;
                            this.normalSearchSplit(bestdc, node, dcs);
                            break findNewDCS;
                        }
                    }else{
                        SDTLog.log(SDTLog.DETAIL, "# Temperature is low... stop SA process - Node : " +node.NODE_ID +" is Leaf node\n");
                        node.NODE_TYPE = NodeType.LEAFNODE;
                        node.setFinalClassLabel();
                        break findNewDCS;
                    }                    
                    temperature *= (1-SDTLearner.coolingRate);                    
                }
            }
        }else{
            updateSizeOfCandidates(seldcs.size());

            DecomposeCandidate bestdc = this.findBestDecomposeCandidate(seldcs);
            SDTLog.log(SDTLog.DETAIL, "# Best Decompose Candidate : " + bestdc.explanation()+"...\n");            
            dcs.remove(bestdc);

            this.loop++;            
            this.normalSearchSplit(bestdc, node, dcs);            
        }
    }
    
    private boolean checkOverSearchCondition(double entropy, double temp){
        double randomProb = Math.random();
        double prob = entropy*temp;
        
        SDTLog.log(SDTLog.DETAIL, " - # Temperature : " + temp + " and Entropy : "+entropy+" / Prob : "+randomProb+" \n");

        if(randomProb <= prob){
            return true;
        }else{
            return false;
        }
    }
    
    private void updateSizeOfCandidates(int size){
         if (maxcandidate < size) {
            maxcandidate = size;
        }

        if (mincandidate > size) {
            mincandidate = size;
        }
    }

    private void normalSearchSplit(DecomposeCandidate bestdc, DNode node, DecomposeCandidateSet dcs) throws IOException {
        Vector<DEdge> edgeset = bestdc.generateEdge(node);
        for (int i = 0; i < edgeset.size(); i++) {
            DEdge edge = edgeset.get(i);
            DNode childnode = edge.getChildNode();
            Refinement bref = edge.getRefinement();
            boolean positiveEdge = bref.getBranchingType();
            if (this.checkLeafNode(childnode)) {
                childnode.NODE_TYPE = NodeType.LEAFNODE;
                childnode.setFinalClassLabel();

            } else {
                childnode.NODE_TYPE = NodeType.BRANCHNODE;
                int refinementType = bestdc.getRefinementType();
                DecomposeCandidateSet newdcs = this.updateExistRefinements(bref, dcs);
                
                if (positiveEdge) {
                    if (refinementType == RefinementType.TOPCONCEPTCONSTRUCTOR) {
                        newdcs.addAll(this.nextDecomposeCandidateSetSearcher(bref, dcs));
                    } else {
                        if (refinementType < RefinementType.IS_EXPANDED) {
                            DecomposeCandidateSet expandDCS = this.nextDecomposeCandidateSetSearcher(bestdc.getPropertyArc(), bref, dcs);                            
                            newdcs.addAll(expandDCS);
                            newdcs.setHistoryPropertySet(expandDCS.getHistoryPropertySet());
                            newdcs.setConnectedPropertySet(expandDCS.getConnectedPropertySet());
                        }
                    }
                }

//                if (!newdcs.isEmpty()) {
                    SDTLog.log(SDTLog.DETAIL, "# Start Decomposing Node : "+ childnode.NODE_ID + " / Entropy : "+ childnode.getEntropy()+" ... \n");
                    decomposeTreeNode(childnode, newdcs);
//                } else {
//                    SDTLog.log(SDTLog.DETAIL, "# Further candidate set is not exist\n");
//                    childnode.NODE_TYPE = NodeType.LEAFNODE;
//                    childnode.setFinalClassLabel();
//                }

            }
        }
    }

    private DecomposeCandidateSet selectRandomDecomposeCandidate(DecomposeCandidateSet origdcs) {
//        System.out.println("+++++++++++++++++++++original dcs+++++++++++++++++++++++++++++++++++++++++++ " + origdcs.size());
//        origdcs.printDecomposeCandidateSet();
        Random seedOfseed = new Random();
        int seed = seedOfseed.nextInt(100);
        Random random = new Random(seed);
        int selectSize = (int) (origdcs.size() * SDTLearner.SELECTRATE);
        if (selectSize < 2) {
            selectSize = 1;
        }
//        System.out.println("select size = " + selectSize);        
        ArrayList<Integer> dcslist = new ArrayList();
        int flag = 0;
        while (true) {
            int index = random.nextInt(origdcs.size());
            if (!dcslist.contains(index)) {
                dcslist.add(index);
                flag++;
            }

            if (selectSize == flag) {
                break;
            }
        }

        DecomposeCandidateSet newdcs = new DecomposeCandidateSet();
        for (int i = 0; i < dcslist.size(); i++) {
            int index = dcslist.get(i);
            newdcs.add(origdcs.get(index));
        }
//        System.out.println("+++++++++++++++++++++new selected dcs+++++++++++++++++++++++++++++++++++++++++++ " + newdcs.size());
//        newdcs.printDecomposeCandidateSet();
        return newdcs;

    }

    private boolean checkLeafNode(DNode node) {

        TargetInstanceValueInformation tivi = node.getTargetInstanceValueInformation();
//        int valueSize = SDTLearner.TARGETVALUESIZE;
        if (tivi.getClassValueSize() == 1) {
            node.setFinalClassLabel();
            return true;
        } else if (node.getInstanceSetSize() < SDTLearner.LEAFSIZE) {
            node.setFinalClassLabel();
            return true;
        } else if (node.getEntropy() <= this.ENTROPY_THRESHOLD) {
            node.setFinalClassLabel();
            return true;
        }

        return false;
    }

    /**
     *
     * @param parentds
     * @param ref
     * @param olddcs
     * @return
     */
    private DecomposeCandidateSet updateExistRefinements(Refinement ref, DecomposeCandidateSet olddcs) {
        DecomposeCandidateSet newdcs = new DecomposeCandidateSet();
        newdcs.setHistoryPropertySet(olddcs.getHistoryPropertySet());
        newdcs.setConnectedPropertySet(olddcs.getConnectedPropertySet());
        
        Vector<TargetInstance> instanceSet = ref.getInstanceSet();
        for (int i = 0; i < olddcs.size(); i++) {
            DecomposeCandidate olddc = olddcs.get(i);
            Vector<Refinement> vset = this.updateRefinements(instanceSet, olddc.getRefinementSet());            
            if(vset.size() > 1){
                DecomposeCandidate newdc = new DecomposeCandidate(vset, olddc.getPropertyArc(), olddc.getRefinementType());
                newdcs.add(newdc);                
            }
        }
        return newdcs;
    }

    private Vector<Refinement> updateRefinements(Vector<TargetInstance> instanceSet, Vector<Refinement> refset) {
        Vector<Refinement> newrefSet = new Vector<Refinement>();

        for (int i = 0; i < refset.size(); i++) {
            Refinement ref = refset.get(i);

            Refinement newref = ref.generateUpdatedRefinement(instanceSet);
            if (!newref.getInstanceSet().isEmpty()) {
                newrefSet.add(newref);
            }
        }
        return newrefSet;
    }

    /**
     * Top concept 으로부터 탐색
     *
     * @param parentds
     * @param ref
     * @param olddcs
     * @return
     */
    private DecomposeCandidateSet nextDecomposeCandidateSetSearcher(Refinement ref, DecomposeCandidateSet olddcs) throws IOException {
        DecomposeCandidateSet newdcs = new DecomposeCandidateSet();
        newdcs.setTargetclass(((ConceptConstructorRefinement) ref).getConditionResource());
        DCSSearcher sdcs = new DCSSearcher(this.ds);
        SDTLog.log(SDTLog.DETAIL, "# Starting Candidate Refinement Search (In top concept..)...\n");
        sdcs.searchDecomposeCandidateSet(newdcs, ref.getInstanceSet(), olddcs.getHistoryPropertySet());
        return newdcs;
    }

    private DecomposeCandidateSet nextDecomposeCandidateSetSearcher(PropertyArc expandArc, Refinement ref, DecomposeCandidateSet olddcs) throws IOException {
        Vector<TargetInstance> instanceSet = ref.getInstanceSet();
        DecomposeCandidateSet expandeddcs = new DecomposeCandidateSet();

        DCSSearcher sdcs = new DCSSearcher(this.ds);
        Vector<PropertyArc> propertySet = new Vector(olddcs.getHistoryPropertySet());
        propertySet.add(expandArc);

        SDTLog.log(SDTLog.DETAIL, "# Starting Candidate Refinement Search (With expanding PA : "+expandArc.getProperty()+" )...\n");
        sdcs.searchDecomposeCandidateSet(expandeddcs, instanceSet, propertySet);
        return expandeddcs;
    }

    private DecomposeCandidate findBestDecomposeCandidate(DecomposeCandidateSet dcs) {      
          DecomposeCandidate firstDC = dcs.firstElement();

        double presentBestValue = firstDC.getInformation_Gain();
//        System.out.println("# 첫번째 refinement : \n" + firstDC.explanation());

        int bestResult = 0;
        for (int present = 1; present < dcs.size(); present++) {
//            System.out.println("------------------------***********" + present + "***********---------------------------------");
            DecomposeCandidate nextDC = dcs.get(present);
//            System.out.println("# next refinements : \n" + nextDC.explanation());
            double nextGainValue = nextDC.getInformation_Gain();

            if (presentBestValue < nextGainValue) {
//                System.out.println("    %% next 가 더 gain이 크다.");
                presentBestValue = nextGainValue;
                bestResult = present;

            } else if (presentBestValue == nextGainValue) {
//                System.out.println("    %% next와 present의 gain이 같다.");
                DecomposeCandidate presentDC = dcs.get(bestResult);
//                SDTLog.log(SDTLog.DETAIL, "# present refinements : \n" + presentDC.explanation() + " \n");
                // 현재 결과 값과 새로운 결과값의 Refinement의 가중치를 비교하여 더 좋은 것으로 선택
                // 확장이 가능한 값이 최대한 선택 되도록 하자 - Cardinalty or Qualification 우선
                if (nextDC.getRefinementType() <= RefinementType.IS_EXPANDED) {
                    if (presentDC.getRefinementType() <= RefinementType.IS_EXPANDED) {
//                        System.out.println("    %% next와 present가 모두 expanded");
                        // 둘다 확장 가능이므로 추가 비교                        
                        //비교방식 1. 더 많이 확장된 리파인먼트를 선택하고 탐색의 범위를 넓히자
                        if (presentDC.getExpansionDepth() < nextDC.getExpansionDepth()) {
//                            System.out.println("        %% next의 깊이가 더 깊다.");
                            presentBestValue = nextGainValue;
                            bestResult = present;
                        } else if (presentDC.getExpansionDepth() == nextDC.getExpansionDepth()) {
                            Random prob = new Random();
                            if (prob.nextDouble() > 0.5) {
                                presentBestValue = nextGainValue;
                                bestResult = present;
                            }
//                            System.out.println("        %% next와 present의 깊이가 같다.");
                            //비교방식 2. 확장의 깊이가 같을경우 프로퍼티의 가중치 비교를 통해 결정하자. 이것도 같으면 아무거나..                            

//                            double presentweight = this.rws.get(presentDC.getProperty());
//                            System.out.println("            %% present weight : " + presentweight);
//                             
//                            double nextweight = this.rws.get(nextDC.getProperty());
//                            System.out.println("            %% next weight : " + nextweight);
//                            if(presentweight < nextweight){
//                                SDTLog.log(SDTLog.DETAIL, "             %% next weight가 더 크다.\n");
                            // 현재는 가중치가 큰게 더 좋은것으로 세팅해 놓음
//                                presentBestValue = nextGainValue;
//                                bestResult = present;                                
//                            }
                        }
                    } else {
                        presentBestValue = nextGainValue;
                        bestResult = present;
                    }
                } else {
                    if (presentDC.getRefinementType() >= RefinementType.IS_EXPANDED) {
//                        System.out.println("???");
                        // 둘다 확장 불가이기 때문에 추가적인 판단 방법이 필요함 (클래스 간의 중요도 비교 등...)                     
                        presentBestValue = nextGainValue;
                        bestResult = present;
                    }
                }
            }
        }

        firstDC = dcs.get(bestResult);
        return firstDC;
    }

    private Vector<Property> updatePropertyHistory(Property p, Vector<Property> old) {
        Vector<Property> propertySet = new Vector<Property>();
        for (int i = 0; i < old.size(); i++) {
            propertySet.add(old.get(i));
        }
        propertySet.add(p);
        return propertySet;
    }
}
